<script lang="ts" setup>
import type { VNodeRef } from 'vue'
import { ref, reactive, onMounted, onBeforeUpdate } from 'vue'
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
import {
  TreeSelectProps,
  CascaderProps,
  SelectProps,
  Empty
} from 'ant-design-vue'
import type { DefaultOptionType } from 'ant-design-vue/lib/vc-cascader'
import { FileInfoAddType, FileUpload } from '@/types/modules/file-info'
import useCol from '@/hooks/system/useCol'
import { useI18n } from '@/hooks/web/useI18n'
import { Status } from '@/utils/http/types.d'
import * as api from '@/api/modules/file-info'
import SparkMD5 from 'spark-md5'

import { useLoginAccountStoreWithOut } from '@/store/modules/login'

import { message } from 'ant-design-vue';
import { UploadOutlined } from '@ant-design/icons-vue';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
import { FileChunk, UploadStatus } from '@/api/modules/file-info'
import axios, { AxiosResponse, CanceledError } from 'axios'

const loginAccountStore = useLoginAccountStoreWithOut()

const { t } = useI18n()

const props = defineProps({
  rank: { type: Number },
  isItem: { type: Boolean, default: false },
  isList: { type: Boolean, default: false },
  itemData: {
    type: Object as PropType<FileUpload>,
    default: () => ({
      files: []
    })
  }
})

const { getBindCol } = useCol(props.isList)

const formRef = ref()
const refs = ref<any[]>([])
const formState = reactive<Expandable<FileInfoAddType>>(props.itemData)

const basicRules = {
  name: [
    { max: 256, message: 'Length Error', trigger: 'blur' },
  ],
}

onBeforeUpdate(() => {
  refs.value = []
})

const setItemRef = (el: VNodeRef): VNodeRef => {
  if (el) {
    refs.value.push(el)
  }
  return el
}

const filterCascaderOptions = (inputValue: string, path: DefaultOptionType[]) =>
  path.some(
    option =>
      (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) >
      -1
  )

const handleBasicOk = async () => {
  let valid = await formRef.value.validate().catch((err: any) => err)
  if (!valid?.errorFields?.length) return valid
  return false
}

const resetBasicForm = () => {
  formRef.value.resetFields()
}

const handleRelateArrOk = async () => {
  let relateArr = []
  let isFailed = false
  for (let i = 0; i < refs.value.length; i++) {
    const result = await (refs.value[i] as any).handleOk()
    relateArr.push(result)
    if (!result) {
      isFailed = true
    }
  }
  if (isFailed) return false
  return relateArr
}

const resetRelateArrForm = () => {
  for (let i = 0; i < refs.value.length; i++) {
    ; (refs.value[i] as any).resetForm()
  }
}

const handleOk = async () => {
  const basicResult = await handleBasicOk()
  const relateArrRsult = await handleRelateArrOk()
  if (basicResult && relateArrRsult) {
    return formState
  }
  return false
}

const resetForm = () => {
  resetBasicForm()
  resetRelateArrForm()
}

const emit = defineEmits(['delete'])
const hanldeDeleteData = () => {
  emit('delete', props.rank)
}

defineExpose({ handleOk, resetForm })

const uploading = ref<boolean>(false);

const progress: UploadProps['progress'] = {
  strokeColor: {
    '0%': '#108ee9',
    '100%': '#87d068',
  },
  strokeWidth: 3,
  format: percent => `${parseFloat((percent as number).toFixed(2))}%`,
  class: 'test',
};

const handleRemove: UploadProps['onRemove'] = file => {
  const index = formState['files'].indexOf(file);
  const newFileList = formState['files'].slice();
  newFileList.splice(index, 1);
  formState['files'] = newFileList;
};

const beforeUpload: UploadProps['beforeUpload'] = file => {
  formState['files'] = [...formState['files'], file];
  return false;
};

const handleUpload = async () => {
  uploading.value = true;

  if (uploadStatus.value != UploadStatus.Pause) {
    _chunksWrapper.value = await createChunks() as FileChunk[]
  } 

  uploadStatus.value = UploadStatus.Uploading;

  await uploadChunks()
};

const chunkSize = 10 * Math.pow(2, 20)
const _chunksWrapper = ref<FileChunk[]>([]);
const uploadStatus = ref<UploadStatus>(UploadStatus.None);
uploadStatus.value = UploadStatus.None

const createChunks = async () => {
  return new Promise(async (resolve) => {
    for (let index = 0; index < formState['files'].length; index++) {
      let file = formState['files'][index];
      uploadStatus.value = UploadStatus.Hashing;
      let hash = await computeHash(file, Math.ceil(file.size / chunkSize))
      uploadStatus.value = UploadStatus.Uploading;

      _chunksWrapper.value?.push({ filename: file.name, chunks: [], hash: hash as string })
      let start = 0;
      let chunkIndex = 0;
      while (start < file.size) {
        let chunk = (file.originFileObj as File).slice(start, start + chunkSize);

        _chunksWrapper.value[index].chunks.push({ Index: chunkIndex, Data: chunk })
        chunkIndex++;
        start += chunkSize;
      }
    }   

    resolve(_chunksWrapper.value)
  })  
}

const uploadChunks = async () => {
  let formDataWrapper: FormData[][] = [];

  for (let index = 0; index < _chunksWrapper.value.length; index++) {
    const file = _chunksWrapper.value[index]

    let slices = (await api.getChunkSlices(file.hash)).data

    if (slices.status == 0) {
      slices.data.forEach((s: number) => {
        file.chunks = file.chunks.filter(x => x.Index != s)
      })
    } else {
      message.success(`秒传:${file.filename}`);
      file.chunks = [];

      continue;
    }
  }
  
  _chunksWrapper.value.forEach(box => {           
    let formDataList = box.chunks.map((item, index) => {      
      let formData = new FormData();
      formData.append('file', item.Data)
      formData.append('fileName', box.filename as string)
      formData.append('chunkName', item.Index.toString())
      formData.append('hash', box.hash)

      return formData;
    })

    formDataWrapper.push(formDataList);
  })

  let requestList: Promise<void>[] = [];

  formDataWrapper.forEach(e => {
    e.forEach(ie => {
      requestList.push(api.uploadChunk(ie, uploadCallback))
    });
  })

  let canceled = false;
  try {
    await Promise.all([...requestList])
  } catch (error: any) {
    if (error.name = 'CanceledError') {      
      canceled = true;
    }
  }
  
  if (!canceled) {
    await mergeChunks().then((_) => {      
      message.success('上传成功');      
    }).catch((_) => {
      message.error('发生未知错误');
    }).finally(() => {
      setTimeout(() => {
        message.destroy()
      }, 3000);
      init_upload_data();
    })
  } else {
    message.info('暂停上传');
    
    uploading.value = false;
  }
}

const mergeChunks = async () => {
  let requestList: Promise<AxiosResponse | undefined>[] = [];

  for (let index = 0; index < _chunksWrapper.value.length; index++) {
    let file = _chunksWrapper.value[index];

    requestList.push(api.mergeChunk(file.hash, file.filename))
  }

  await Promise.all([...requestList])
}

const computeHash = (file: any, chunkCount: number) => {
  return new Promise((resolve, reject) => {    
    let chunks = chunkCount;
    let currentChunk = 0;
    let spark = new SparkMD5.ArrayBuffer();
    let fileReader = new FileReader();

    fileReader.onload = function (e) {
      spark.append(e.target?.result);
      currentChunk++;
      if (currentChunk < chunks) {
        loadNext();
      } else {
        let md5 = spark.end();
        resolve(md5);
      }
    };
    fileReader.onerror = function (e) {
      reject(e);
    };
    function loadNext() {
      let start = currentChunk * chunkSize;
      let end = start + chunkSize;
      if (end > file.size) {
        end = file.size;
      }
      fileReader.readAsArrayBuffer((file.originFileObj as File).slice(start, end));
    }
    loadNext();
  });
}

const pause = async () => {  
  if (uploadStatus.value == UploadStatus.Uploading) {
    api.cancelUpload()
    uploadStatus.value = UploadStatus.Pause
        
  } else if (uploadStatus.value == UploadStatus.Pause) {
    await handleUpload();    
  }
}

const uploadedCount = ref(0);
const percent_instance = ref(0.0);

const uploadCallback = () => {  
  uploadedCount.value++;
  percent_instance.value = parseInt((uploadedCount.value / getChunkTotalCount() * 100).toFixed(0));  
}

const getChunkTotalCount = (): number => {
  let size = 0;
  _chunksWrapper.value.forEach(e => {
    size += e.chunks.length
  })

  return size;
}

const init_upload_data = () => {
  _chunksWrapper.value = [];
  formState['files'] = [];
  uploading.value = false;
  uploadStatus.value = UploadStatus.None;
  uploadedCount.value = 0;
  percent_instance.value = 0.0;
}

</script>
<template>
  <div :class="['form-container', { isChild: isItem }]">
    <div class="form-wrapper">
      <a-form ref="formRef" :model="formState" :rules="basicRules" :labelWrap="true" @finish="handleOk">       
        <a-upload v-model:file-list="formState['files']" name="files" :before-upload="beforeUpload" @remove="handleRemove" :progress="progress" :multiple="true">
          <a-button>
            <upload-outlined></upload-outlined>
            选择文件
          </a-button>
        </a-upload>
        <a-button type="primary" :disabled="formState['files'].length === 0" :loading="uploading" style="margin-top: 16px"
          @click="handleUpload">
          {{ uploading
          ? uploadStatus == UploadStatus.Hashing 
          ? '哈希计算中' : '上传中' : '开始上传'
}}
        </a-button>

        <a-progress v-if="uploading" :stroke-color="{ from: '#108ee9', to: '#87d068', }" :strokeWidth="5" :percent="percent_instance" status="active" />
        <a-button v-if="uploadStatus != UploadStatus.None" type="primary" style="margin-top: 16px" id="btn-pause"
          @click="pause">
          <span>{{ uploadStatus == UploadStatus.Pause ? '继续上传' : '暂停上传' }}</span>
        </a-button>
      </a-form>
    </div>
    <div v-if="isItem" class="del">
      <a-popconfirm :title="t('system.deletePrompt')" placement="top" @confirm="hanldeDeleteData">
        <delete-outlined :style="{ fontSize: '16px', color: '#f00' }" />
      </a-popconfirm>
    </div>
  </div>
</template>

<style lang="less" scoped>
.form-container {
  &.isChild {
    margin: 0;
    border: 1px solid @border-color-base;
    padding-top: 18px;
    border-radius: 4px;
    margin-bottom: 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;

    .del {
      margin: 0 20px;
      cursor: pointer;
    }

    .form-wrapper {
      flex-grow: 2;
    }
  }
}

.ant-picker,
.ant-input-number {
  width: 100%;
}

.ant-form > .ant-btn-primary[disabled] {
 color: rgb(221, 221, 221);
 background-color: #0a6dc3;
}
</style>
